#include "stdafx.h"
#include "log/formatter.hpp"
#include "log/message.hpp"
#include "stdlib/exception.hpp"
#include "stdlib/datetime.hpp"
#include "stdlib/datetimeformat.hpp"
#include "stdlib/datetimeformatter.hpp"
#include "stdlib/timestamp.hpp"
#include "stdlib/timezone.hpp"
using namespace JHCPP::stdlib;
using namespace JHCPP::stdlib::jhException;
#include "utils/numberformatter.hpp"
using namespace JHCPP::utils;
#include "filesys/environment.hpp"
using JHCPP::filesys::CEnvironment;


namespace JHCPP
{
	namespace log
	{
		//////////////////////////////////////////////////////////////////////////
		//CFormatter
		//////////////////////////////////////////////////////////////////////////
		CFormatter::CFormatter()
		{
		}

		CFormatter::~CFormatter()
		{
		}

		void CFormatter::setProperty(const std::string& name, const std::string& value)
		{
			throw PropertyNotSupportedException();
		}

		std::string CFormatter::getProperty(const std::string& name) const
		{
			throw PropertyNotSupportedException();
		}


		//////////////////////////////////////////////////////////////////////////
		//CPatternFormatter
		//////////////////////////////////////////////////////////////////////////
		const std::string CPatternFormatter::PROP_PATTERN = "pattern";
		const std::string CPatternFormatter::PROP_TIMES   = "times";

		CPatternFormatter::CPatternFormatter() : m_localTime(false)
		{
		}

		CPatternFormatter::CPatternFormatter(const std::string& format) : m_localTime(false), m_pattern(format)
		{
		}

		CPatternFormatter::~CPatternFormatter()
		{
		}

		void CPatternFormatter::format(const CMessage& msg, std::string& text)
		{
			CTimeStamp timestamp = msg.getTime();
			if (m_localTime)
			{
				timestamp += CTimeZone::utcOffset()*CTimeStamp::resolution();
				timestamp += CTimeZone::dst()*CTimeStamp::resolution();
			}
			CDateTime dateTime = timestamp;
			std::string::const_iterator it  = m_pattern.begin();
			std::string::const_iterator end = m_pattern.end();
			while (it != end)
			{
				if (*it == '%')
				{
					if (++it != end)
					{
						switch (*it)
						{
						case 's': text.append(msg.getSource()); break;
						case 't': text.append(msg.getText()); break;
						case 'l': CNumberFormatter::append(text, (int) msg.getPriority()); break;
						case 'p': text.append(getPriorityName((int) msg.getPriority())); break;
						case 'q': text += getPriorityName((int) msg.getPriority()).at(0); break;
						case 'P': CNumberFormatter::append(text, msg.getPid()); break;
						case 'T': text.append(msg.getThread()); break;
						case 'I': CNumberFormatter::append(text, msg.getTid()); break;
						case 'N': text.append(CEnvironment::nodeName()); break;
						case 'U': text.append(msg.getSourceFile() ? msg.getSourceFile() : ""); break;
						case 'u': CNumberFormatter::append(text, msg.getSourceLine()); break;
						case 'w': text.append(CDateTimeFormat::WEEKDAY_NAMES[dateTime.dayOfWeek()], 0, 3); break;
						case 'W': text.append(CDateTimeFormat::WEEKDAY_NAMES[dateTime.dayOfWeek()]); break;
						case 'b': text.append(CDateTimeFormat::MONTH_NAMES[dateTime.month() - 1], 0, 3); break;
						case 'B': text.append(CDateTimeFormat::MONTH_NAMES[dateTime.month() - 1]); break;
						case 'd': CNumberFormatter::append0(text, dateTime.day(), 2); break;
						case 'e': CNumberFormatter::append(text, dateTime.day()); break;
						case 'f': CNumberFormatter::append(text, dateTime.day(), 2); break;
						case 'm': CNumberFormatter::append0(text, dateTime.month(), 2); break;
						case 'n': CNumberFormatter::append(text, dateTime.month()); break;
						case 'o': CNumberFormatter::append(text, dateTime.month(), 2); break;
						case 'y': CNumberFormatter::append0(text, dateTime.year() % 100, 2); break;
						case 'Y': CNumberFormatter::append0(text, dateTime.year(), 4); break;
						case 'H': CNumberFormatter::append0(text, dateTime.hour(), 2); break;
						case 'h': CNumberFormatter::append0(text, dateTime.hourAMPM(), 2); break;
						case 'a': text.append(dateTime.isAM() ? "am" : "pm"); break;
						case 'A': text.append(dateTime.isAM() ? "AM" : "PM"); break;
						case 'M': CNumberFormatter::append0(text, dateTime.minute(), 2); break;
						case 'S': CNumberFormatter::append0(text, dateTime.second(), 2); break;
						case 'i': CNumberFormatter::append0(text, dateTime.millisecond(), 3); break;
						case 'c': CNumberFormatter::append(text, dateTime.millisecond()/100); break;
						case 'F': CNumberFormatter::append0(text, dateTime.millisecond()*1000 + dateTime.microsecond(), 6); break;
						case 'z': text.append(CDateTimeFormatter::tzdISO(m_localTime ? CTimeZone::tzd() : CDateTimeFormatter::UTC)); break;
						case 'Z': text.append(CDateTimeFormatter::tzdRFC(m_localTime ? CTimeZone::tzd() : CDateTimeFormatter::UTC)); break;
						case 'E': CNumberFormatter::append(text, msg.getTime().epochTime()); break;
						case '[':
							{
								++it;
								std::string prop;
								while (it != end && *it != ']') prop += *it++;
								if (it == end) --it;
								try
								{
									text.append(msg[prop]);
								}
								catch (...)
								{
								}
								break;
							}
						default: text += *it;
						}
						++it;
					}
				}
				else text += *it++;
			}
		}

		void CPatternFormatter::setProperty(const std::string& name, const std::string& value)
		{
			if (name == PROP_PATTERN)
				m_pattern = value;
			else if (name == PROP_TIMES)
				m_localTime = (value == "local");
			else 
				CFormatter::setProperty(name, value);
		}

		std::string CPatternFormatter::getProperty(const std::string& name) const
		{
			if (name == PROP_PATTERN)
				return m_pattern;
			else if (name == PROP_TIMES)
				return m_localTime ? "local" : "UTC";
			else
				return CFormatter::getProperty(name);
		}

		static std::string priorities[] = 
		{
			"",
			"Fatal",
			"Critical",
			"Error",
			"Warning",
			"Notice",
			"Information",
			"Debug",
			"Trace"
		};

		const std::string& CPatternFormatter::getPriorityName(int prio)
		{
			jh_assert (1 <= prio && prio <= 8);	
			return priorities[prio];
		}
	}//end of namespace log
}//end of namespace JHCPP
